package mMap.hashDemo;

import org.junit.Test;
import utils.ThreadUtils;

import java.util.*;

/**
* @author jie
* @version 创建时间：2017年11月30日 下午10:09:53
 *
 *
 *
 *
 * “equals”与“==”、“hashCode”的区别和使用场景？
 *
 * ==
 * 基本数据类型: 用==比较的是数据的值。
 * 引用类型: 用比较的是他们在内存中的存放地址。【只比较地址。是 JVM分配的。不可变】
 * 对象引用地址是 JVM 分配的：引用地址在对象创建后不可更改。
 *
 *
 * equals:
 * 默认:Object的equals方法。判断地址(this == obj);
 * 自定义的equals ：只调用equals方法。没有调用 hashcode方法。
 *
 *
 * hashCode:
 * Java 哈希函数计算得到的
 * 主要用途：散列的key
 * 事实上，Object类提供的默认实现确实保证每个对象的hash码不同（在对象的内存地址基础上经过特定算法返回一个hash码）。
 *
 *
 * HashSet.add(Object)
 * 结论: hashset.add(Obj) 去重。每次都先调用 hashCode。如果相同 == (对象地址相同)，再调用 equals。
 *
 *
 * 有一个约定：HashSet
 * 判断对象承载的信息是否相等。分步骤比较
 *
 * hashCode相等，对象地址不一定、equasls不一定。
 * 	 对象地址相等。是一个对象。肯定一致。
 * 	 对象地址不等。不确定。
 * 		 obj equals 不一定相等（hash碰撞）
 *
 *
 *  hashmap<k,v>
 *      v 的 hashcode 变化。不影响。get  .
 *  		k 的 hashcode 变化会影响 k v的get
* HashCode定义
 
（1）HashCode的存在主要是用于查找的快捷性，如Hashtable，HashMap等，HashCode是用来在散列存储结构中确定对象的存储地址的；
（2）如果两个对象相同， equals方法一定返回true，并且这两个对象的HashCode一定相同；
（3）如果对象的equals方法被重写，那么对象的HashCode也尽量重写，并且产生HashCode使用的对象，一定要和equals方法中使用的一致，否则就会违反上面提到的第2点；
（4）两个对象的HashCode相同，并不一定表示两个对象就相同，也就是equals方法不一定返回true，只能够说明这两个对象在散列存储结构中，如Hashtable，他们存放在同一个篮子里。
*
*我们知道冲突的产生是由于不同的对象产生了相同的散列码，假如我们设计对象的散列码可以确保99.999999999%的不重复，
*但是有一种 绝对且几乎不可能遇到的冲突你是绝对避免不了的。
*我们知道hashcode返回的是int，它的值只可能在int范围内。
*如果我们存放的数据超过了int的范围呢？这样就必定会产生两个相同的index，这时在index位置处会存储两个对象，
*我们就可以利用key本身来进行判断。所以具有相索引的对象，在该index位置处存在多个对象，我们必须依靠key的hashCode和key本身来进行区分。
*/
public class HashEqualsClassDemo {

	//非hash元素替换了。怎么判断。
	@Test
	public void fieldChanged() {
	  System.out.println("fieldChanged:");
		Person p1 = new Person(11, 1, "张三",38);

		Person p3 = new Person(11, 1, "张三",18);//与上面 p1 hashcode 相同、equals 相同
		System.out.println("HashEqualsClassDemo:fieldChanged:"+(p1.equals(p3)));

		HashSet<Person> set = new HashSet<>();
		set.add(p1);
		System.out.println("HashEqualsClassDemo:fieldChanged: have:"+set.contains(p3));

		boolean add = set.add(p3);
		System.out.println("HashEqualsClassDemo:fieldChanged:add:" + add);

		for (Person p: set) {
			System.out.println("HashEqualsClassDemo:fieldChanged:hashset:"+p.toString());
		}
	}

	public static void main(String[] args) {
//		String aString = "a";
//		aString.equals("");
//		System.out.println(aString.hashCode());
		Set<Person> set = new HashSet<Person>();
		Person p1 = new Person(11, 1, "张三");
		Person p2 = new Person(12, 1, "李四");
		Person p3 = new Person(11, 1, "张三");//与上面 p1 hashcode 相同、equals 相同
		Person p4 = new Person(11, 1, "李四");
		// 只验证p1、p3
		System.out.println("p1 == p1? 相同对象（数据相同）相比:" + (p1 ==p1));//结果：true. 结论：：对象== 是比较地址。【没有调用 hashcode 方法 or equals 方法】， 虽然 hashcode 相同、equals 相同。
		ThreadUtils.sleep(500);
		System.out.println("p1 == p3? 不同对象（数据相同）相比2:" + (p1 == p3));//结果：false. 结论：：对象== 是比较地址。【没有调用 hashcode 方法 or equals 方法】， 虽然 hashcode 相同、equals 相同。
		ThreadUtils.sleep(500);
		System.out.println("p1.equals(p3)?:" + p1.equals(p3));//结果：true。结论：只调用equals方法。没有调用 hashcode方法。
		ThreadUtils.sleep(500);
		System.out.println("-----------------------分割线--------------------------");
		System.out.println("Set 集合添加元素.......");

		//结论 hashset.add(Obj) 去重。每次都先调用 hashCode。如果相同 == (对象地址相同)，再调用 equals。
		set.add(p1);//调用hashCode方法
		set.add(p1);//调用hashCode方法...hashCode相同。没有调用。equals 方法。 应该是用了 == 比较了对象被分配的 JVM 地址。
		set.add(p2);//调用hashCode方法
		set.add(p3);//调用hashCode方法...hashCode相同。调用equals方法......也相同。【舍弃】【保留的旧的对象】
		set.add(p4);//调用hashCode方法
		System.out.println("set.size()=" + set.size());
		//从上图可以看出，程序调用 5 次hashCode方法，一次equals方法，其set的长度只有3。正确
		//add方法运行流程完全符合他们两者之间的处理流程。
		Iterator<Person> iterator = set.iterator();
		while (iterator.hasNext()){
			Person next = iterator.next();
			System.out.println("HashEqualsClassDemo:main:set value:保留的p1："+(next == p1)+"  or 保留的p3 :"+(next == p3));
		}

		System.err.println("-----------------------分割线  test hash code changed--------------------------");

		ThreadUtils.sleep(200);

		HashMap<Integer, Person> integerPersonHashMap = new HashMap<>();
		integerPersonHashMap.put(1,p1);
		p1.setName(p1.getName()+"...");
		Person person = integerPersonHashMap.get(1);
		if (person != null) {
			System.out.println("p:main:>>>>"+person.toString());
		}else {
			System.err.println("Demo1:main:null");
		}

		HashMap< Person,Integer> map2 = new HashMap<>();
		map2.put(p2,1);
		p2.setName(p2.getName()+"...");
		Integer integer2 = map2.get(p2);
		System.out.println("Demo1:main:>>>>map2.get(p2): "+integer2);
	}
}

class Person {
	private int age;
	private int sex; // 0：男，1：女
	private String name;
	private final int PRIME = 37;
	private int type;

	Person(int age, int sex, String name) {
		this.age = age;
		this.sex = sex;
		this.name = name;
	}
	Person(int age, int sex, String name,int type) {
		this.age = age;
		this.sex = sex;
		this.name = name;
		this.type = type;
	}

	@Override
	public int hashCode() {
		int hash = Objects.hash(age, sex, name, PRIME);
		System.out.println("调用hashCode方法...........name:" + name + ",hashCode:" + hash);
		return hash;
	}

	@Override
	public boolean equals(Object o) {
		super.equals(o);
		System.out.println("调用equals方法...........");
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		Person person = (Person) o;
		return age == person.age && sex == person.sex && PRIME == person.PRIME && Objects.equals(name, person.name);
	}

	//	/**
//	* 重写hashCode()
//	 */
//	public boolean equals(Object obj) {
//		System.out.println("调用equals方法...........");
//		if (obj == null) {
//			return false;
//		}
//		if (obj.getClass() != this.getClass()) {
//			return false;
//		}
//		if (this == obj) {
//			return true;
//		}
//		Person person = (Person) obj;
//		if (getAge() != person.getAge() || getSex() != person.getSex()) {
//			return false;
//		}
//		if (getName() != null) {
//			if (!getName().equals(person.getName())) {
//				return false;
//			}
//		} else if (person != null) {
//			return false;
//		}
//		return true;
//	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public int getSex() {
		return sex;
	}

	public void setSex(int sex) {
		this.sex = sex;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getPRIME() {
		return PRIME;
	}

	@Override
	public String toString() {
		return "Person{" +
				"age=" + age +
				", sex=" + sex +
				", name='" + name + '\'' +
				", PRIME=" + PRIME +
				", type=" + type +
				'}';
	}
}